#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <unistd.h>
#include <netinet/ip_icmp.h>
#include <netinet/ip.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/time.h>
#include <ctime>

#define BUFSIZE 1500

using namespace std;

unsigned short checksum(void* b, int len) {
    unsigned short* buf = (unsigned short*)b;
    unsigned int sum = 0;
    while (len > 1) {
        sum += *buf++;
        len -= 2;
    }
    if (len == 1) {
        sum += *(unsigned char*)buf;
    }
    sum = (sum >> 16) + (sum & 0xFFFF);
    sum += (sum >> 16);
    return ~sum;
}

int main() {
    int sockfd;
    char buf[BUFSIZE];

    sockfd = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
    if (sockfd < 0) {
        perror("socket");
        return 1;
    }

    int one = 1;
    if (setsockopt(sockfd, IPPROTO_IP, IP_HDRINCL, &one, sizeof(one)) < 0) {
        perror("setsockopt");
        close(sockfd);
        return 1;
    }

    cout << "ICMP Time Server started. Waiting for requests..." << endl;

    while (true) {
        struct sockaddr_in cliaddr;
        socklen_t len = sizeof(cliaddr);

        ssize_t n = recvfrom(sockfd, buf, BUFSIZE, 0, (struct sockaddr*)&cliaddr, &len);
        if (n < 0) {
            perror("recvfrom");
            continue;
        }

        struct ip* iphdr = (struct ip*)buf;
        int iphdrlen = iphdr->ip_hl << 2;
        struct icmp* icmphdr = (struct icmp*)(buf + iphdrlen);

        if (icmphdr->icmp_type == ICMP_ECHO) {
            char sendbuf[BUFSIZE];
            struct ip* reply_ip = (struct ip*)sendbuf;
            struct icmp* reply_icmp = (struct icmp*)(sendbuf + sizeof(struct ip));

            reply_ip->ip_v = 4;
            reply_ip->ip_hl = 5;
            reply_ip->ip_tos = 0;
            reply_ip->ip_len = htons(20 + 8 + 56);
            reply_ip->ip_id = htons(54321);
            reply_ip->ip_off = 0;
            reply_ip->ip_ttl = 64;
            reply_ip->ip_p = IPPROTO_ICMP;
            reply_ip->ip_src.s_addr = iphdr->ip_dst.s_addr;
            reply_ip->ip_dst.s_addr = iphdr->ip_src.s_addr;

            reply_icmp->icmp_type = ICMP_ECHOREPLY;
            reply_icmp->icmp_code = 0;
            reply_icmp->icmp_id = icmphdr->icmp_id;
            reply_icmp->icmp_seq = icmphdr->icmp_seq;

            struct timeval client_time;
            memcpy(&client_time, icmphdr->icmp_data, sizeof(client_time));

            struct timeval server_time;
            gettimeofday(&server_time, NULL);
            memcpy(reply_icmp->icmp_data, &client_time, sizeof(client_time));
            memcpy(reply_icmp->icmp_data + sizeof(client_time), &server_time, sizeof(server_time));

            int data_size = 56 - 2 * sizeof(client_time);
            if (data_size > 0) {
                memset(reply_icmp->icmp_data + 2 * sizeof(client_time), 0, data_size);
            }

            reply_icmp->icmp_cksum = 0;
            reply_icmp->icmp_cksum = checksum(reply_icmp, 8 + 56);

            if (sendto(sockfd, sendbuf, 20 + 8 + 56, 0, (struct sockaddr*)&cliaddr, len) < 0) {
                perror("sendto");
            }
            else {
                cout << "Sent reply to " << inet_ntoa(cliaddr.sin_addr) << endl;
            }
        }
    }

    close(sockfd);
    return 0;
}